package cn.parser;

import cn.dao.NeoDao;
import cn.dao.impl.NeoDaoImpl;
import cn.structure.BaseEntity;
import cn.structure.ClassEntity;
import cn.structure.ConstructorCallEntity;
import cn.structure.ConstructorEntity;
import cn.structure.InvocationEntity;
import cn.structure.MethodEntity;
import cn.structure.edp.EdpEntity;
import cn.utils.NeoUtil;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;

/**
 * Neo4j 解析器，将实体映射为节点和关系
 *
 * @author Tao Lu
 */
public class NeoParser {
    private static NeoParser instance = new NeoParser();
    private static GraphDatabaseService service = NeoUtil.getService();
    private static NeoDao dao = NeoDaoImpl.getInstance();

    public static NeoParser getInstance() {
        return instance;
    }

    /**
     * 解析后使用，通过 spoonParser 引用的节点实体、方法实体创建图
     *
     * @param spoonParser 解析管理器实例
     */
    public void createGraph(SpoonParser spoonParser) {
        try (Transaction tx = service.beginTx()) {
            spoonParser.getClassEntities().forEach(entity -> dao.createClassNode(tx, entity));
            spoonParser.getExecutableEntities().forEach(entity -> {
                if (entity instanceof MethodEntity) {
                    dao.createMethodNode(tx, (MethodEntity) entity);
                } else if (entity instanceof ConstructorEntity) {
                    dao.createConstructorNode(tx, (ConstructorEntity) entity);
                }
            });
            spoonParser.getAbstractInvocationEntities().forEach(entity -> {
                if (entity instanceof InvocationEntity) {
                    dao.createInvocationNode(tx, (InvocationEntity) entity);
                } else if (entity instanceof ConstructorCallEntity) {
                    dao.createConstructorCallNode(tx, (ConstructorCallEntity) entity);
                }
            });
            spoonParser.getTypeParameterEntities().forEach(entity -> dao.createTypeParameterNode(tx, entity));
            spoonParser.getVariableEntities().forEach(entity -> dao.createVariableNode(tx, entity));
            spoonParser.getRelationEntities().forEach(relationEntity -> {
                BaseEntity source_entity = relationEntity.getSource();
                BaseEntity target_entity = relationEntity.getTarget();
                Node source_node = dao.getNodeByAstPath(tx, source_entity.getEntityType(), source_entity.getAst_path());
                Node target_node = dao.getNodeByAstPath(tx, target_entity.getEntityType(), target_entity.getAst_path());
                Relationship relationship = dao.createRelation(source_node, target_node, relationEntity.getRelation_type());
                if (relationEntity.getProperties() != null) {
                    relationEntity.getProperties().forEach(relationship::setProperty);
                }
            });
            tx.commit();
        }
    }

    public void addEdp(EdpParser edpParser) {
        try (Transaction tx = service.beginTx()) {
            edpParser.getEdpEntities().forEach(entity -> {
                Node source_node = dao.createEdpNode(tx, entity);
                edpParser.getEdpRelationEntities().forEach(relationEntity -> {
                    BaseEntity target_entity = relationEntity.getTarget();
                    if (entity.equals(relationEntity.getSource())) {
                        Node target_node = dao.getNodeByAstPath(tx, target_entity.getEntityType(), target_entity.getAst_path());
                        if (target_node == null && target_entity instanceof ClassEntity) {
                            target_node = dao.createClassNode(tx, (ClassEntity) target_entity);
                        }
                        Relationship relationship = dao.createRelation(source_node, target_node, relationEntity.getRelation_type());
                        if (relationEntity.getProperties() != null) {
                            relationEntity.getProperties().forEach(relationship::setProperty);
                        }
                    }
                });
            });
            tx.commit();
        }
    }

    public void addIpc(IpcParser ipcParser) {
        try (Transaction tx = service.beginTx()) {
            ipcParser.getIpcEntities().forEach(entity -> {
                Node source_node = dao.createIpcNode(tx, entity);
                ipcParser.getIpcRelationEntities().forEach(relationEntity -> {
                    if (entity.equals(relationEntity.getSource())) {
                        long target_id = ipcParser.getEdpNodeId((EdpEntity) relationEntity.getTarget());
                        Relationship relationship = dao.createRelation(source_node, tx.getNodeById(target_id), relationEntity.getRelation_type());
                        if (relationEntity.getProperties() != null) {
                            relationEntity.getProperties().forEach(relationship::setProperty);
                        }
                    }
                });
            });
            tx.commit();
        }
    }
}
